<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TinyEventLoop - Java I/O Models Explained</title>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            line-height: 1.6;
            max-width: 900px;
            margin: 0 auto;
            padding: 20px;
            color: #333;
            background-color: #f9f9f9;
        }

        h1,
        h2,
        h3 {
            color: #2c3e50;
        }

        h1 {
            border-bottom: 2px solid #3498db;
            padding-bottom: 10px;
        }

        h2 {
            margin-top: 30px;
            border-left: 4px solid #3498db;
            padding-left: 10px;
        }

        code {
            font-family: 'Consolas', 'Monaco', monospace;
            background-color: #f0f0f0;
            padding: 2px 4px;
            border-radius: 3px;
            font-size: 0.95em;
        }

        pre {
            background-color: #f5f5f5;
            border-left: 3px solid #3498db;
            padding: 15px;
            overflow-x: auto;
            line-height: 1.4;
            border-radius: 0 4px 4px 0;
        }

        .note {
            background-color: #e7f5fe;
            border-left: 4px solid #3498db;
            padding: 12px;
            margin: 15px 0;
            border-radius: 0 4px 4px 0;
        }

        .diagram {
            background-color: white;
            border: 1px solid #ddd;
            padding: 15px;
            margin: 15px 0;
            text-align: center;
        }

        table {
            width: 100%;
            border-collapse: collapse;
            margin: 20px 0;
        }

        th,
        td {
            border: 1px solid #ddd;
            padding: 8px 12px;
            text-align: left;
        }

        th {
            background-color: #f2f2f2;
        }

        .io-model {
            margin-bottom: 30px;
        }
    </style>
</head>

<body>
    <h1>TinyNIOServer</h1>

    <div class="io-model">
        <h2>Java阻塞式IO</h2>
        <p>Java语言提供的IO机制以基于字节流的<code>InputStream/OutputStream</code>、基于编解码的<code>Reader/Writer</code>为主，这些IO机制基于阻塞式<code>IO(Blocking I/O)</code>，常见的Scanner就是典型；
        </p>

        <p>当你执行如下代码时，Scanner会阻塞，等待用户输入（键盘打字输入，以Enter结束），然后调用<code>readInt</code>、<code>ReadLine</code>方法来试图解码用户的输入;</p>

        <pre><code>Scanner inputScanner = new Scanner(System.in);
if(inputScanner.hasNextLine()){
    String line = inputScanner.nextLine();
    System.out.printf("接收到用户输入:%s\n",line);
}</code></pre>

        <p>这种<code>Scanner</code>读取用户输入就是经典的阻塞式I/O，因为程序无法预估用户的输入、无法预估用户输入的时机，所以Scanner通过以下两种方式来应对：</p>

        <ol>
            <li><strong>针对用户输入</strong>：通过内置各种基于正则表达式的<code>pattern</code>来匹配用户输入，本质上是一个基于确定性有限状态机(<code>FSM</code>)的停机问题；
                <pre><code>private String digits = "0123456789abcdefghijklmnopqrstuvwxyz";
private String non0Digit = "[\\p{javaDigit}&&[^0]]";</code></pre>
            </li>
            <li><strong>针对用户输入时机</strong>：<code>Scanner</code>会首先从<code>InBuffer</code>中获取输入，如果<code>Buffer</code>中没有匹配<code>pattern</code>的输入，则通过调用<code>readInput</code>函数（发起系统调用<code>read(buf)</code>）挂起自己（将当前线程在kernel中对应的<code>task_struct</code>状态置为<code>Pending</code>）。
                <ul>
                    <li>键盘控制器会通过内置的buffer缓冲用户的输入，在用户键入<code>Enter</code>时，发起CPU中断;</li>
                    <li>kernel会将当前Java线程对应的<code>task_struct</code>置为<code>Runnable</code>;</li>
                    <li>在下一次内核调度器调度至此<code>task_struct</code>时，当前Java线程从等待中恢复，继续执行;</li>
                    <li>当前Java线程调用<code>findPatternInBuffer</code>匹配，若有对应输入，返回；</li>
                </ul>
                <pre><code>// Search for the pattern
while (true) {
    String token = findPatternInBuffer(pattern, horizon);
    if (token != null) {
        matchValid = true;
        return token;
    }
    if (needInput)
        readInput();
    else
        break; // up to end of input
}
//readInput通过操作系统调用执行
n = source.read(buf);</code></pre>
            </li>
        </ol>
    </div>

    <div class="io-model">
        <h2>非阻塞IO</h2>
        <p>通过上述阻塞式IO
            <code>Scanner</code>的例子我们可以看到，阻塞式IO的Java线程需要一个宿主操作系统执行单元<code>task_struct</code>执行，当<code>Scanner</code>内部调用<code>readInput</code>时，JVM执行操作系统调用<code>read(buf)</code>，然后当前Java线程对应的<code>task_struct</code>会被挂起，等到键盘缓存区得到足够的输入后，键盘控制器发起中断，kernel重新调度当前<code>task_struct</code>，才可以执行接下来的代码；
        </p>

        <p>通过上述的步骤我们发现，在这个过程中，在进行阻塞式IO时，每一个Java线程要对应一个宿主操作系统的执行单元<code>task_struct</code>；上述的<code>Scanner</code>从<code>System.in</code>中读取数据，如果要实现同时读取<code>System.out</code>与<code>System.err</code>，就需要总共三个Java线程才可以；
        </p>

        <div class="note">
            <p>这种情况下，IO多路复用技术被引入进来，通过select系统调用，一个进程可以监听多个FD(File Descriptor，是操作系统对于底层资源的抽象)；</p>
        </div>

        <p>在select系统调用引入IO多路复用技术的基础上，epoll/kqueue又通过不同的机制优化了select效率：高效的数据结构（红黑树）、减少FD数据传输（只传输就绪的FD，支持水平触发LT和边缘触发ET），使得epoll/kqueue成为了如今高性能IO的核心之一；
        </p>
    </div>
</body>

</html>